﻿/*****************************************************************************\
 *             University of Illinois/NCSA Open Source License
 *
 * 
 *      Copyright © 2012,2013, The Board of Trustees of the University of Illinois. All rights reserved.
 *
 *      Developed by: Rakesh Bobba, Derek Dagit, Erich Heine, Denis Kholine,
 *                    Klara Nahrstedt, David Rodgers,   William H. Sanders, Tim Yardley
 *
 *      Information Trust Institute/Coordinated Science Laboratory
 * 
 *      University of Illinois
 *      
 *      The Board of Trustees of the University of Illinois
 *      109 Coble Hall
 *      801 South Wright Street
 *      Champaign, IL 61820-6242
 *
 *      http://www.iti.illinois.edu / http://csl.illinois.edu
 * 
 *      Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 *      associated documentation files (the “Software”), to deal with the Software without restriction, 
 *      including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *      and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 *      subject to the following conditions:
 *
 *      Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *      following disclaimers.
 *      
 *      Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *      the following disclaimers in the documentation and/or other materials provided with the distribution.
 *
 *      Neither the names of Information Trust institute, Coordinated Science Laboratory, University of Illinois,
 *      The Board of Trustees of the University of Illinois nor the names of its contributors may be used to 
 *      endorse or promote products derived from this Software without specific prior written permission.
 *      
 *      THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 *      LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN
 *      NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 *      SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
 *      
 \*****************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
﻿using System.Threading;
using SIEGate.Core.Legacy;

namespace SIEGate.Core
{
    // LINEAR AdapterContainer. Will be unhappy with other topologies
    // Note - the Register is what link()s the last adapter in the
    //        chain to this. Just cuz it is easer. Later, if we get
    //        and unlink in the IAdapter interface we can fix it.
    public class AdapterContainer : IDataProcessor
    {
        public class AdapterContainerInfo : ConfigInfo, IAdapterContainerInfo
        {
            private HashSet<IAdapterInfo> m_memberinfos = new HashSet<IAdapterInfo>();
            private Type m_adapterContainerType = typeof(AdapterContainer);


            public ISet<IAdapterInfo> MemberInfos
            {
                get
                {
                    return m_memberinfos;
                }
            }

            public override Type RuntimeType
            {
                get
                {
                    return m_adapterContainerType;
                }
                set
                {
                    if (typeof(AdapterContainer).IsAssignableFrom(value))
                    {
                        m_adapterContainerType = value;
                    }
                    else
                    {
                        throw new NotSupportedException("Must assign an AdapterContainer to AdapterContainerInfo.RuntimeType");
                    }
                }
            }
            public void AddAdapter(IAdapterInfo toAdd)
            {
                m_memberinfos.Add(toAdd);
            }

            public ISet<Guid> IncomingStreamIDs
            {
                get
                {
                    return m_memberinfos.First().IncomingStreamIDs;
                }
                set
                {
                    throw new NotImplementedException("No mucking with containters. Deal with Adapters instead");
                }
            }

            public ISet<Guid> OutgoingStreamIDs
            {
                get
                {
                    return m_memberinfos.Last().OutgoingStreamIDs;
                }
                set
                {
                    throw new NotImplementedException("No mucking with containers. Deal with adapters instead");
                }
            }

            public string Name
            {
                get
                {
                    return "AC: " + MemberInfos.Select(ci => ci.Name).
                                    Aggregate((os, e) => os + e);
                }
                set
                {
                }
            }

            public uint ID
            {
                get;
                set;
            }

            public int InitializationTimeout
            {
                get;
                set;
            }

            public event EventHandler<GSF.EventArgs<string>> Log;

            public void OnLog(string format, params object[] args)
            {
                throw new NotImplementedException();
            }

            public Dictionary<string, string> Settings
            {
                get;
                set;
            }

            public Type AdapterType
            {
                get
                {
                    throw new NotImplementedException();
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            public IAdapter MyInstance
            {
                get;
                set;
            }

            public override void Update(Dictionary<string, string> configInfo)
            {
                throw new NotImplementedException();
            }

            public override void ExtractConfiguration(System.Data.DataSet configurationTables)
            {
                throw new NotImplementedException();
            }

            public AdapterContainerInfo(IConfigInfo left)
            {
                ISet<IAdapterInfo> leftside;
                if (left is IAdapterContainerInfo)
                {
                    leftside = (left as IAdapterContainerInfo).MemberInfos;
                }
                else if (left is IAdapterInfo)
                {
                    leftside = new HashSet<IAdapterInfo> { left as IAdapterInfo };
                }
                else
                {
                    throw new ArgumentException("AdapterContainerInfo only acceps IConfigs that are IAdapterInfo and IAdapterContaintainerInfo");
                }
                foreach (IAdapterInfo info in leftside)
                {
                    m_memberinfos.Add(info);
                }
            }
            public void Consolidate(IConfigInfo right)
            {
                ISet<IAdapterInfo> rightside;
                if (right is IAdapterContainerInfo)
                {
                    rightside = (right as IAdapterContainerInfo).MemberInfos;
                }
                else if (right is IAdapterInfo)
                {
                    rightside = new HashSet<IAdapterInfo>() { right as IAdapterInfo };
                }
                else
                {
                    throw new ArgumentException("Consolidate only acceps IConfigs that are IAdapterInfo and IAdapterContaintainerInfo");
                }
                if (rightside.First() is ISmartAdapterInfo)
                {
                    throw new ArgumentException("SmartAdapters must be first in the AdapterChain.");
                }
                foreach (IAdapterInfo info in rightside)
                {
                    m_memberinfos.Add(info);
                }
            }
        }

        private volatile bool m_running = false;
        protected INotificationReceiver Receiver;

        private IMessageBusProxy bus;

        private IAdapterContainerInfo m_info;

        public IAdapterContainerInfo Info
        {
            get
            {
                return m_info;
            }
        }

        public INotificationReceiver SignalProxy { get; set; }
        private List<IAdapter> adapterChain = new List<IAdapter>();

        private void OutputMeasurements(params IMeasurement[] output)
        {
            bus.Send(output);
        }

        public void Link(IAdapter next)
        {

            if (adapterChain.Count() != 0)
            {
                var adapter = next as ISmartAdapter;
                if (adapter != null)
                {
                    if (!((ISmartAdapterInfo)adapter.Info).StaticInputs)
                    {
                        throw new InvalidOperationException("Something broke in the configurator: smart adapters with changing inputs need to be first in a chain, or on thier own");
                    }
                    adapter.ChangeInputs += AdapterContainer_ChangeInputs;
                }

                adapterChain.Last().Emit += next.Collect;

            }
            adapterChain.Add(next);
            Info.MemberInfos.Add(next.Info);
        }

        void AdapterContainer_ChangeInputs(object sender, EventArgs e)
        {
            ISet<Guid> adds = new HashSet<Guid>(adapterChain.First().IncomingStreamIDs.Except(bus.Subscribed));
            ISet<Guid> removes = new HashSet<Guid>(bus.Subscribed.Except(adapterChain.First().IncomingStreamIDs));
            bus.Subscribe(adds);
            bus.UnSubscribe(removes);
        }

        public void Register()
        {
            this.adapterChain.Last().Emit += this.OutputMeasurements;
            // TODO: Find a mechanism where legacy and other sink adapters can start without downstream
            // subscriptions.
            foreach (IAdapter adapter in adapterChain)
            {
                if (adapter is LegacyAdapterBase)
                {
                    this.bus.Subscribe(adapterChain.First().IncomingStreamIDs);
                }
            }
            //this.bus.Subscribe(this.adapterChain.First().IncomingStreamIDs);
            // HACK fix qos
            if (this.adapterChain.Last().OutgoingStreamIDs.Any())
            {
                this.bus.Publish(this.adapterChain.Last().OutgoingStreamIDs);
            }
            this.bus.StartSubscription += bus_StartSubscription;
            this.bus.EndSubscription += bus_EndSubscription;
        }

        private ISet<Guid> SourcesFor(Guid sid)
        {
            ISet<Guid> chainSources = new HashSet<Guid>() { sid };
            foreach (IAdapter i in adapterChain.AsEnumerable().Reverse())
            {
                // optimize... have a private "is smart path thing
                if (i is ISmartAdapter)
                {
                    chainSources = ((ISmartAdapter)i).GetInputsFor(chainSources);
                }
                else
                {
                    // once we encounter a "dumb adapter" there is no need to continue tavelling the chain,
                    // because everything that will need to be subscribed is already coming in.
                    chainSources = new HashSet<Guid>(adapterChain.First().IncomingStreamIDs);
                    break;
                }
            }
            return chainSources;
        }
        void bus_EndSubscription(object sender, MessageBusSubscriptionEventArgs e)
        {
            ISet<Guid> offCandidates = SourcesFor(e.StreamID);

            HashSet<Guid> stillNeeded = new HashSet<Guid>();
            foreach (Guid s in bus.ActivePubs.Except(new HashSet<Guid>() { e.StreamID }))
            {
                stillNeeded.UnionWith(SourcesFor(s));
            }
            offCandidates.ExceptWith(stillNeeded);
            bus.UnSubscribe(offCandidates);
        }

        void bus_StartSubscription(object sender, MessageBusSubscriptionEventArgs e)
        {
            ISet<Guid> toTurnOn = SourcesFor(e.StreamID);

            toTurnOn.ExceptWith(bus.Subscribed);
            bus.Subscribe(toTurnOn);

        }



        // this needs to change, temporary for example purposes
        public void doit()
        {
            IMeasurement[] input = this.bus.Recv();
            if (input.Length == 0)
            {
                return;
            }
            this.adapterChain.First().Collect(input);
            //IMeasurement m = this.bus.Recv();
            //if (m != null) this.adapterChain.collect(m);
        }

        public void Start()
        {
            m_running = true;
            while (m_running)
            {
                doit();
            }
        }
        public void Stop()
        {
            m_running = false;
            //TODO: clean up stuff here too
        }



        // TODO: HACK this currently requires the adapter infos to be instantiated!
        public AdapterContainer(IMessageBusProxy bus, IAdapterContainerInfo info)
        {
            if (!(info is AdapterContainerInfo))
            {
                throw new ArgumentException("Bad info type for AdapterContainer");
            }
            m_info = info;
            this.bus = bus;

            Receiver = Info.Receiver;
            Info.Receiver = null;

            foreach (IAdapterInfo a in info.MemberInfos)
            {
                Link(a.MyInstance);
            }
        }
    }
}
